package it.webrules.deploymanager.core.dy.deploy.tomcat;

import it.webrules.deploymanager.core.dy.deploy.DeployException;
import it.webrules.deploymanager.core.dy.deploy.MBeanManager;

import java.io.IOException;
import java.util.Locale;
import java.util.Set;

import javax.management.AttributeNotFoundException;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.remote.JMXConnector;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * Manage Tomcat's MBean
 * 
 * @author dometec
 *
 */
public class TomcatMBeanManager implements MBeanManager {

	private static final String PREFIX_WEBMODULE_NAME = ":j2eeType=WebModule,name=//";
	private static final String SUFFIX_WEBMODULE_NAME = ",J2EEApplication=none,J2EEServer=none";

	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	public static final int ONESEC_IN_MS = 1000;

	public static final int STAGE_NEW = 0;
	public static final int STAGE_PARSE = 1;
	public static final int STAGE_PREPARE = 2;
	public static final int STAGE_SERVICE = 3;
	public static final int STAGE_ENDINPUT = 4;
	public static final int STAGE_ENDOUTPUT = 5;
	public static final int STAGE_KEEPALIVE = 6;
	public static final int STAGE_ENDED = 7;

	private ObjectName ajpConnector;
	private String engineName = "Catalina";
	private MBeanServerConnection jmxServerConnection;

	/**
	 * Create a connection with tomcat's instance and search for
	 * 
	 * @param jmxConnector a valid jmxConnetor to a running tomcat's instance
	 * @throws IOException 
	 * @throws NullPointerException 
	 * @throws MalformedObjectNameException 
	 * @throws ReflectionException 
	 * @throws MBeanException 
	 * @throws InstanceNotFoundException 
	 * @throws AttributeNotFoundException 
	 * @throws DeployException 
	 */
	public TomcatMBeanManager(JMXConnector jmxConnector, String engineName) throws IOException, MalformedObjectNameException,
			NullPointerException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException,
			DeployException {

		this.engineName = engineName;
		this.jmxServerConnection = jmxConnector.getMBeanServerConnection();

		findAJPConnector();
	}

	@Override
	public final void findAJPConnector() throws MalformedObjectNameException, IOException, MBeanException, AttributeNotFoundException,
			InstanceNotFoundException, ReflectionException, DeployException {

		logger.info("finding AJP/1.3 connector...");

		ObjectName on = new ObjectName(engineName + ":type=Connector,port=*");
		Set<ObjectName> queryNames = jmxServerConnection.queryNames(on, null);
		for (ObjectName oi : queryNames) {
			String protocol = (String) jmxServerConnection.getAttribute(oi, "protocol");
			if (protocol != null && protocol.toLowerCase(Locale.ENGLISH).contains("ajp")) {
				ajpConnector = oi;
				logger.info("Found " + ajpConnector + ".");
				break;
			}
		}

		if (ajpConnector == null)
			throw new DeployException("AJP/1.3 Not Found!.");

	}

	@Override
	public ObjectName getAJPGlobalRequestProcessorObjectName() throws MalformedObjectNameException, IOException, MBeanException,
			AttributeNotFoundException, InstanceNotFoundException, ReflectionException {

		String grp = engineName + ":type=GlobalRequestProcessor";
		return new ObjectName(grp + ",name=*" + ajpConnector.getKeyProperty("port") + "*");

	}

	@Override
	public void pauseAJP() throws InstanceNotFoundException, MBeanException, ReflectionException, IOException,
			MalformedObjectNameException, AttributeNotFoundException {

		logger.info("Pausing AJP Connector...");

		jmxServerConnection.invoke(ajpConnector, "pause", null, null);

		logger.info("AJP Connector paused.");
	}

	@Override
	public void stopAJP() throws InstanceNotFoundException, MBeanException, ReflectionException, IOException, MalformedObjectNameException,
			AttributeNotFoundException {

		logger.info("Stopping AJP Connector...");

		jmxServerConnection.invoke(ajpConnector, "stop", null, null);

		logger.info("AJP Connector stopped.");
	}

	@Override
	public void resumeAJP() throws InstanceNotFoundException, MBeanException, ReflectionException, IOException,
			MalformedObjectNameException, AttributeNotFoundException, DeployException {

		logger.info("Resuming AJP Connector...");

		jmxServerConnection.invoke(ajpConnector, "resume", null, null);

		logger.info("AJP Connector resumed.");

	}

	@Override
	public void startAJP() throws InstanceNotFoundException, MBeanException, ReflectionException, IOException,
			MalformedObjectNameException, AttributeNotFoundException, DeployException {

		logger.info("Starting AJP Connector...");

		jmxServerConnection.invoke(ajpConnector, "start", null, null);

		logger.info("AJP Connector started.");

	}

	/**
	 * 
	 * Wait until all request are finished
	 * 
	 * @param timeout sec before going to timeout
	 * @return true when all request are done, false on timeout
	 * @throws NullPointerException 
	 * @throws MalformedObjectNameException 
	 * @throws IOException 
	 * @throws InterruptedException 
	 * @throws ReflectionException 
	 * @throws MBeanException 
	 * @throws InstanceNotFoundException 
	 * @throws AttributeNotFoundException 
	 */
	@Override
	public boolean waitForProcessingAllRequest(Integer timeout) throws MalformedObjectNameException, NullPointerException, IOException,
			InterruptedException, AttributeNotFoundException, InstanceNotFoundException, MBeanException, ReflectionException {

		logger.info("Wait ends of all RequestProcessor... (timeout: {} sec.).", timeout);

		String rp = engineName + ":type=RequestProcessor";
		String objectname = rp + ",worker=*" + ajpConnector.getKeyProperty("port") + "*,name=*";
		ObjectName on = new ObjectName(objectname);

		while (timeout > 0) {

			String ex1 = null;
			int countActiveRequest = 0;

			try {
				Set<ObjectName> queryNames = jmxServerConnection.queryNames(on, null);
				for (ObjectName onRequestProcessor : queryNames) {
					Integer stage = (Integer) jmxServerConnection.getAttribute(onRequestProcessor, "stage");
					if (stage != STAGE_ENDED) {
						countActiveRequest++;
						ex1 = onRequestProcessor.getCanonicalName();
					}
				}
			} catch (Exception e) {
				// ignoro, elavorazione solo informativa
			}

			if (countActiveRequest > 0) {
				logger.debug("There are " + countActiveRequest + " active RequestProcessor (timeout in " + timeout + " sec, " + ex1
						+ "...).");
			} else {
				logger.info("Completato.");
				return true;
			}

			timeout--;
			Thread.sleep(ONESEC_IN_MS);
		}

		logger.warn("Not all RequestProcessor are finisched in {} sec.", timeout);
		return false;

	}

	@Override
	public void undeploy(String virtualhost, String contextname) throws MalformedObjectNameException, NullPointerException, IOException,
			InstanceNotFoundException, MBeanException, ReflectionException, DeployException {

		logger.info("Undeploing...");

		ObjectName on = new ObjectName(engineName + ":type=WebModule,name=//" + virtualhost + "/" + contextname + SUFFIX_WEBMODULE_NAME);

		if (!jmxServerConnection.isRegistered(on)) {

			logger.warn("The application wasn't deployed before.");

		} else {
			jmxServerConnection.invoke(ajpConnector, "destroy", null, null);

			if (jmxServerConnection.isRegistered(on))
				throw new DeployException("Undeploy failed!");

			logger.info("Undeployed.");
		}

	}

	@Override
	public boolean isDeployed(String virtualhost, String contextname) throws MalformedObjectNameException, NullPointerException,
			IOException, InterruptedException, DeployException {

		ObjectName on = new ObjectName(engineName + PREFIX_WEBMODULE_NAME + virtualhost + "/" + contextname + SUFFIX_WEBMODULE_NAME);

		if (!jmxServerConnection.isRegistered(on)) {
			logger.info("The application " + contextname + " is't deployed.");
			return false;
		}

		logger.info("The application {} is deployed.", contextname);
		return true;

	}

	// TODO forse non serve, il thread che invoca l'undeploy attende l'effettivo
	// undeploy della webapp
	@Override
	public void waidUndeploy(String virtualhost, String contextname, Integer timeout) throws MalformedObjectNameException,
			NullPointerException, IOException, InterruptedException, DeployException {

		logger.info("Wait undeploy {}... (timeout: {} sec.).", contextname, timeout);

		while (timeout > 0) {
			ObjectName on = new ObjectName(engineName + PREFIX_WEBMODULE_NAME + virtualhost + "/" + contextname + SUFFIX_WEBMODULE_NAME);
			if (!jmxServerConnection.isRegistered(on)) {
				logger.info("Undeployed.");
				return;
			}
			timeout--;
			Thread.sleep(ONESEC_IN_MS);
		}

		throw new DeployException("Timeout while deploing!");
	}

	@Override
	public void waidDeploy(String virtualhost, String contextname, Integer timeout) throws MalformedObjectNameException,
			NullPointerException, IOException, InterruptedException, DeployException {

		logger.info("Wait deploy {}... (timeout: {} sec.).", contextname, timeout);

		while (timeout > 0) {
			ObjectName on = new ObjectName(engineName + PREFIX_WEBMODULE_NAME + virtualhost + "/" + contextname + SUFFIX_WEBMODULE_NAME);

			try {

				if (jmxServerConnection.isRegistered(on)) {
					logger.info("Deployed.");
					return;
				}

			} catch (Exception e) {
				// waiting....
			}

			timeout--;
			Thread.sleep(ONESEC_IN_MS);
		}

		throw new DeployException("Timeout while deploing!");
	}

	@Override
	public Object getMBeanValue(String objectName, String attribute) throws Exception {
		return getMBeanValue(objectName, attribute, null);
	}

	@Override
	public Object getMBeanValue(String objectName, String attribute, String subattribute) throws Exception {

		Object out = null;
		ObjectName on = new ObjectName(objectName);
		Object object = jmxServerConnection.getAttribute(on, attribute);

		if (object instanceof CompositeDataSupport) {
			CompositeDataSupport cds = (CompositeDataSupport) object;
			out = cds.get(subattribute);
		}

		// TODO else in caso of not CompositeDataSupport

		logger.debug("Value of mbean '{}' is '{}'.", objectName + ";" + attribute + ";" + subattribute, out);

		return out;
	}

	@Override
	public boolean isAvailable() {

		try {

			jmxServerConnection.getMBeanCount();
			logger.trace("JMX Avalilable.");
			return true;

		} catch (Exception e) {
			logger.trace("JMX NOT Avalilable.");
			return false;
		}

	}

}